'use strict';
var VIEWER = VIEWER || {};
VIEWER.container;
VIEWER.width = 0;
VIEWER.height = 0;
VIEWER.destroy = false;

VIEWER.scene;
VIEWER.world;
VIEWER.camera;
VIEWER.controls;

VIEWER.initial_zoom = 5;

VIEWER.directional_light1;
VIEWER.directional_light2;
VIEWER.directional_light3;
VIEWER.directional_light4;
VIEWER.light1_pos = new THREE.Vector3(10, 10, 10);
VIEWER.light2_pos = new THREE.Vector3(-5, 10, 5);
VIEWER.light3_pos = new THREE.Vector3(0, -10, 5);
VIEWER.light4_pos = new THREE.Vector3(0, 0, -10);
VIEWER.last_cam_pos;
VIEWER.last_cam_rot;

VIEWER.orig_geometry;
VIEWER.subd_geometry;
VIEWER.render_geometry;

VIEWER.callbacks = {
    modelStateChange: null,
    textureStateChange: null,
    updateMeshInfo: null,
    updateViewerPreviews: null
};

VIEWER.materials = [{
    textures: new Object(),
    settings: new Object()
}];
VIEWER.mat_shading_modes = [];
VIEWER.mat_overrides = [{
    textures: new Object(),
    settings: new Object()
}];

VIEWER.viewer_materials = {
    normal: {
        shader: 'normal',
        textures: ['diff', ['spec', 'spec_glossiness'], ['reflection', 'reflection_mask'], 'glow', 'ao', 'norm', 'bump', 'ddiff', 'dnorm', 'dbump'],
    },
    shadesphere: {
        shader: 'shadesphere',
        textures: ['shadesphere', 'norm', 'bump', 'dnorm', 'dbump'],
    },
    shadeless: {
        shader: 'shadeless',
        textures: ['diff', 'ddiff'],
        force_settings: {
            diff_str: 1.0,
            diff_tex_str: 1.0,
        },
    },
};
VIEWER.viewer_shading_modes = {
    wire: {
        type: 'wire',
        color: function (s) {
            return VIEWER.getShaderColor(s.diff_col, 0.75).getHex();
        },
        setting_bindings: ['diff_col'],
        mat_types: {
            shadesphere: {
                force_settings: {
                    diff_col: 'FFFFFF',
                },
            }
        },
    },
    xray: {
        type: 'shader',
        doublesided: true,
        depthtest: false,
        transparent: true,
        wireonname: 'wireonxray',
        wirecolor: 0xbebebe,
        force_shader: 'xray',
        force_settings: {
            diff_col: '2bb0d8',
            diff_str: 1.0,
            diff_tex_str: 1.0,
        },
    },
    shadeless: {
        type: 'shader',
        wireonname: 'wireonshadeless',
        wirecolor: 0x888888,
        force_shader: 'shadeless',
        mat_types: {
            shadesphere: {
                force_settings: {
                    diff_col: 'FFFFFF',
                },
            },
        },
        force_settings: {
            diff_str: 1.0,
            diff_tex_str: 1.0,
            diff_alpha: 1.0,
        },
    },
    smooth: {
        type: 'shader',
        wireonname: 'wireonsmooth',
        wirecolor: 0x888888,
    },
    clean: {
        type: 'shader',
        wireonname: 'wireonclean',
        force_shader: 'normal',
        disable_textures: ['diff', 'ddiff', 'ao'],
        mat_types: {
            shadesphere: {
                force_settings: {
                    ao_col: 'FFFFFF',
                    spec_col: 'FFFFFF',
                    spec_str: 0.15,
                    spec_shininess: 0.1,
                },
            },
            shadeless: {
                force_settings: {
                    ao_col: 'FFFFFF',
                    spec_col: 'FFFFFF',
                    spec_str: 0.15,
                    spec_shininess: 0.1,
                },
            }
        },
        force_settings: {
            diff_str: 0.5,
            diff_col: 'FFFFFF',
            diff_alpha: 1.0,
        },
    },
};
VIEWER.viewer_shading_mode_keys = Object.keys(VIEWER.viewer_shading_modes);
(function () {
    for (var i = 0; i < VIEWER.viewer_shading_mode_keys.length; i++) {
        var key = VIEWER.viewer_shading_mode_keys[i];
        VIEWER.viewer_shading_modes[key].key = key;
    }
})();

VIEWER.shader_texture_settings = {
    ao: {
        param: 'tAO',
        defines: [{
            d: 'USE_AMBIENT_OCCLUSION',
            frag: true
        }],
        enable_flag: 'enableAO',
    },
    bump: {
        param: 'bumpMap',
        defines: [{
            d: 'USE_BUMPMAP',
            frag: true
        }, {
            d: 'USE_BUMP_DERIVATIVES',
            frag: true
        }],
        skip_filter: true,
        enable_flag: 'enableBump',
    },
    dbump: {
        param: 'detailBumpMap',
        defines: [{
            d: 'USE_DETAIL_BUMPMAP',
            frag: true
        }, {
            d: 'USE_BUMP_DERIVATIVES',
            frag: true
        }],
        skip_filter: true,
        enable_flag: 'enableDetailBump',
    },
    diff: {
        param: 'tDiffuse',
        defines: [{
            d: 'USE_DIFFUSE_TEXTURE',
            frag: true
        }],
        enable_flag: 'enableDiffuse',
    },
    ddiff: {
        param: 'tDetailDiffuse',
        defines: [{
            d: 'USE_DETAIL_DIFFUSE_TEXTURE',
            frag: true
        }],
        enable_flag: 'enableDetailDiffuse',
    },
    shadesphere: {
        param: 'tShadesphere',
        defines: [],
        skip_filter: true,
        enable_flag: 'enableShadesphere',
    },
    norm: {
        param: 'tNormal',
        defines: [{
            d: 'USE_NORMAL_MAP',
            frag: true
        }, {
            d: 'USE_TANGENTS',
            frag: true,
            vert: true
        }],
        enable_flag: 'enableNormal',
    },
    dnorm: {
        param: 'tDetailNormal',
        defines: [{
            d: 'USE_DETAIL_NORMAL_MAP',
            frag: true
        }, {
            d: 'USE_TANGENTS',
            frag: true,
            vert: true
        }],
        enable_flag: 'enableDetailNormal',
    },
    spec: {
        param: 'tSpecular',
        defines: [{
            d: 'USE_SPEC_TEX',
            frag: true
        }],
        enable_flag: 'enableSpecular',
    },
    spec_glossiness: {
        param: 'tSpecularShine',
        defines: [{
            d: 'USE_SPEC_SHINE_TEX',
            frag: true
        }],
        enable_flag: 'enableSpecularShine',
    },
    glow: {
        param: 'tGlow',
        defines: [{
            d: 'USE_GLOW',
            frag: true
        }],
        enable_flag: 'enableGlow',
    },
    reflection: {
        param: 'tReflection',
        defines: [{
            d: 'USE_REFLECTION',
            frag: true
        }],
        enable_flag: 'enableReflection',
    },
    reflection_mask: {
        param: 'tReflectionMask',
        defines: [{
            d: 'USE_REFLECTION_MASK',
            frag: true
        }],
        enable_flag: 'enableReflectionMask',
    },
};
VIEWER.shader_uniform_mapping = {
    uAlphaClip: {
        bindings: ['alpha_clip'],
        mapping: 'float',
    },
    uAOColor: {
        bindings: ['ao_col'],
        mapping: 'color',
    },
    uAOStrength: {
        bindings: ['ao_str'],
        mapping: 'float',
    },
    uDiffuseColor: {
        bindings: ['diff_str', 'diff_tex_str', 'diff_col'],
        mapping: function (s, mat_idx, mode_def) {
            var diff_intensity = VIEWER.shadingMode_hasTexture(mode_def, mat_idx, 'diff') ? s.diff_tex_str : s.diff_str;
            return VIEWER.getShaderColor(s.diff_col, diff_intensity);
        },
    },
    uDiffuseAlpha: {
        bindings: ['diff_alpha'],
        mapping: 'float',
    },
    uHueShift: {
        bindings: ['shadesphere_hue'],
        mapping: 'float',
    },
    uSaturationShift: {
        bindings: ['shadesphere_saturation'],
        mapping: 'float',
    },
    uLightnessShift: {
        bindings: ['shadesphere_lightness'],
        mapping: 'float',
    },
    uDetailDiffuseStrength: {
        bindings: ['ddiff_str'],
        mapping: 'float',
    },
    uDetailDiffuseRepeat: {
        bindings: ['ddiff_repeat'],
        mapping: 'float',
    },
    uNormalScale: {
        bindings: ['norm_str'],
        mapping: 'float',
    },
    uDetailNormalStrength: {
        bindings: ['dnorm_str'],
        mapping: 'float',
    },
    uDetailNormalRepeat: {
        bindings: ['dnorm_repeat'],
        mapping: 'float',
    },
    bumpScale: {
        bindings: ['bump_str'],
        mapping: function (s) {
            return s.bump_str * 0.1;
        },
    },
    uDetailBumpStrength: {
        bindings: ['dbump_str'],
        mapping: function (s) {
            return s.dbump_str * 0.1;
        },
    },
    uDetailBumpRepeat: {
        bindings: ['dbump_repeat'],
        mapping: 'float',
    },
    uSpecularColor: {
        bindings: ['spec_col', 'spec_str'],
        mapping: function (s) {
            return VIEWER.getShaderColor(s.spec_col, s.spec_str);
        },
    },
    uShininess: {
        bindings: ['spec_shininess'],
        mapping: function (s) {
            return s.spec_shininess * 255.0;
        },
    },
    uGlowStrength: {
        bindings: ['glow_str'],
        mapping: 'float',
    },
    uGlowColor: {
        bindings: ['glow_col'],
        mapping: 'color',
    },
    uGlowDiffuseInfluence: {
        bindings: ['glow_diffuse'],
        mapping: 'float',
    },
    uReflectionStrength: {
        bindings: ['reflection_str'],
        mapping: 'float',
    },
    uReflectionColor: {
        bindings: ['reflection_col'],
        mapping: 'color',
    },
    uReflectionMaskStrength: {
        bindings: ['reflection_mask_str'],
        mapping: 'float',
    },
    uReflectionCoefficient: {
        bindings: ['reflection_fresnel'],
        mapping: function (s) {
            return Math.pow(s.reflection_fresnel, 2.0) * 8.0;
        },
    },
};
VIEWER.shader_params = {
    normal: {
        threejs_lights: true,
    },
    shadesphere: {
        threejs_lights: false,
    },
    shadeless: {
        threejs_lights: false,
    },
    xray: {
        threejs_lights: false,
    },
};
VIEWER.disabled_textures = {};
VIEWER.maximum_textures = 32;
VIEWER.texture_priority_order = ['diff', 'norm', 'bump', 'spec'];
//初始化总入口
VIEWER.initViewer = function (elemId) {
    VIEWER.container = document.getElementById(elemId);
    
    VIEWER.width = window.innerWidth-200;  
    VIEWER.height = window.innerHeight-125;
    VIEWER.destroy = false;
    if (!Detector.webgl) {
        Detector.addGetWebGLMessage();
        return;
    }
    
    VIEWER.renderer_init(VIEWER.container);
    $(window).resize(function () {
        VIEWER.width = window.innerWidth-200;  //减去navbar的高度
        VIEWER.height = window.innerHeight-125;  //减去sidebar的宽度
        VIEWER.renderer_setSize(VIEWER.width, VIEWER.height);
        VIEWER.renderer_render();
    });
    VIEWER.renderer_render();
    VIEWER.animate();
};


VIEWER.renderer_init = function (context_container) {

    VIEWER.scene = new THREE.Scene();
    VIEWER.world = new THREE.Object3D();
    VIEWER.scene.add(VIEWER.world);
    if (!VIEWER.renderer) {
        VIEWER.renderer = new THREE.WebGLRenderer({
            antialias: true,  //开启反锯齿
            alpha: true       //设置背景色透明
        });
        VIEWER.renderer.gammaInput = true;
        VIEWER.renderer.gammaOutput = true;
        VIEWER.renderer.physicallyBasedShading = true;
        VIEWER.renderer.sortObjects = false;
        VIEWER.renderer.autoClear = true;
        VIEWER.renderer.domElement.className = 'radial';
    }
    VIEWER.renderer.setSize(VIEWER.width, VIEWER.height);
    context_container.appendChild(VIEWER.renderer.domElement);

    VIEWER.camera = new THREE.PerspectiveCamera(45, VIEWER.width / VIEWER.height, 1, 1000);

    VIEWER.controls = new THREE.TrackballControls(VIEWER.camera, VIEWER.renderer.domElement);

    VIEWER.camera_setDefault(VIEWER.camera, false, VIEWER.controls, VIEWER.initial_zoom);
    VIEWER.scene.add(VIEWER.camera);

    VIEWER.last_cam_pos = VIEWER.camera.position.clone();
    VIEWER.last_cam_rot = VIEWER.camera.rotation.clone();
    VIEWER.camera.updateMatrixWorld(true);
    var ambient = new THREE.AmbientLight(0x000000);
    VIEWER.scene.add(ambient);
    VIEWER.directional_light1 = new THREE.DirectionalLight(0xfffaf0, 1.15);
    VIEWER.scene.add(VIEWER.directional_light1);
    VIEWER.directional_light2 = new THREE.DirectionalLight(0xb3e5ff, .55);
    VIEWER.scene.add(VIEWER.directional_light2);
    VIEWER.directional_light3 = new THREE.DirectionalLight(0xfdffcc, .55);
    VIEWER.scene.add(VIEWER.directional_light3);
    VIEWER.directional_light4 = new THREE.DirectionalLight(0xb3e5ff, .55);
    VIEWER.scene.add(VIEWER.directional_light4);
    VIEWER.setLightsForCamera(VIEWER.camera);


    var pointLight = new THREE.PointLight(0xffffff, 1.0);
    pointLight.position.copy(VIEWER.camera.position);
    VIEWER.camera.add(pointLight);
};

VIEWER.renderer_setSize = function (width, height) {

    VIEWER.renderer.setSize(width, height);
    VIEWER.camera.aspect = width / height;
    if (typeof VIEWER.controls.handleResize === 'function') {
        VIEWER.controls.handleResize();
    }
    VIEWER.camera.updateProjectionMatrix();
};

VIEWER.renderer_render = function () {
    //P3DV.scene_update();
    VIEWER.renderer.render(VIEWER.scene, VIEWER.camera);
};

VIEWER.animate = function () {
    if (VIEWER.destroy)
        return;


    requestAnimationFrame(VIEWER.animate);
    //if (P3DV.show_stats && P3DV.stats) P3DV.stats.update();
    //var cam_move_len = P3DV.camera.position.clone().sub(P3DV.last_cam_pos).length();
    //if (cam_move_len < 1e-5 && P3DV.camera.rotation.equals(P3DV.last_cam_rot) && P3DV.force_render_frames-- < 1) {
    VIEWER.controls.update();
    //    if (!(P3DV.viewer_settings.autorotate || P3DV.autorot_slow || P3DV.reset_phase || P3DV.render_anims.length)) return;
    //}
    // P3DV.camera.near = P3DV.camera_getNearClip(P3DV.camera);
    //var near_diff = Math.max(0.001, P3DV.camera.near) / Math.max(0.001, P3DV.camera_previous_z_near);
    //if (!(near_diff < 1.25 && near_diff > 0.8)) {
    //    P3DV.camera.updateProjectionMatrix();
    //    P3DV.camera_previous_z_near = P3DV.camera.near;
    //}
    //var c = P3DV.camera.position.clone();
    //c.normalize();
    //var angle = Math.acos(P3DV.last_cam_pos.dot(c) / P3DV.last_cam_pos.length() / c.length());
    //if (angle) {
    //    P3DV.setLightsForCamera(P3DV.camera);
    //    if (P3DV.viewer_settings.autorotate && angle > 1e-5)
    //        P3DV.rotate_lock = new Date().getTime() + 1500;
    //}
    //P3DV.last_cam_pos = P3DV.camera.position.clone();
    //P3DV.last_cam_rot = P3DV.camera.rotation.clone();
    VIEWER.renderer_render();
};

VIEWER.fitAll = function (node) {
    // Calculate bounding box of the whole scene
    var boundingBoxOfNode = new THREE.Box3().setFromObject(node);

    // Refocus camera the center of the given object
    var centerOfGravity = boundingBoxOfNode.center();
    var newCameraPosition = new THREE.Vector3();
    newCameraPosition.subVectors(centerOfGravity, VIEWER.controls.target);
    VIEWER.camera.position.addVectors(VIEWER.camera.position, newCameraPosition);
    VIEWER.camera.lookAt(centerOfGravity);
    VIEWER.controls.target.set(centerOfGravity.x, centerOfGravity.y, centerOfGravity.z);

    // Move camera along z until the object fits into the screen
    var sphereSize = boundingBoxOfNode.size().length() * 0.5;
    var distToCenter = sphereSize / Math.sin(Math.PI / 180.0 * VIEWER.camera.fov * 0.5);
    var target = VIEWER.controls.target;
    var vec = new THREE.Vector3();
    vec.subVectors(VIEWER.camera.position, target);
    vec.setLength(distToCenter);
    VIEWER.camera.position.addVectors(vec, target);
};

VIEWER.callbacks.modelStateChange = function () {
    if (VIEWER.loadStates.model_loading)
        VIEWER.model.status = 'loading';
    else
        VIEWER.model.status = 'ready';
};

VIEWER.callbacks.updateViewerPreviews = function (force) {
    //P3d.viewerSync.updateViewerPreviews(force);
};

VIEWER.loadStates = {
    model_rev: 0,
    model_loading: false,
    textures_rev: new Array(),
    textures_loading: new Array(),
};
VIEWER.loadStates.reset = function () {
    this.model_rev++;
    this.textures_rev = new Array();
    this.textures_loading = new Array();
};
VIEWER.loadStates.modelCallback = function () {
    if (VIEWER.callbacks.modelStateChange)
        VIEWER.callbacks.modelStateChange();
}
VIEWER.loadStates.modelSetLoading = function () {
    this.model_rev++;
    this.model_loading = true;
    this.modelCallback();
    return this.model_rev;
};
VIEWER.loadStates.modelSetReady = function (rev) {
    if (rev == this.model_rev) {
        this.model_loading = false;
        this.modelCallback();
        return true;
    }
    return false;
};
VIEWER.loadStates.textureCallback = function () {
    if (VIEWER.callbacks.textureStateChange)
        VIEWER.callbacks.textureStateChange();
}
VIEWER.loadStates.resetTextures = function (idx) {
    this.textures_loading[idx] = new Array();
    if (this.textures_rev[idx]) {
        this.textures_rev[idx]++;
    } else {
        this.textures_rev[idx] = 1;
    }
    this.textureCallback();
    return this.textures_rev[idx];
};
VIEWER.loadStates.textureSetLoading = function (url, idx, rev) {
    if (rev == this.textures_rev[idx]) {
        this.textures_loading[idx].push(url);
        this.textureCallback();
    }
};
VIEWER.loadStates.textureSetReady = function (url, idx, rev) {
    if (rev == this.textures_rev[idx]) {
        for (var i = 0; i < this.textures_loading[idx].length; i++) {
            if (this.textures_loading[idx][i] == url) {
                this.textures_loading[idx].splice(i, 1);
                break;
            }
        }
        this.textureCallback();
        return true;
    }
    return false;
};


VIEWER.camera_setDefault = function (camera, world, controls, init_distance) {
    if (camera) {
        camera.position = new THREE.Vector3(0, 0, init_distance);
        camera.rotation.set(0, 0, 0);
        camera.up = new THREE.Vector3(0, 1, 0);
    }
    if (world) {
        world.rotation.set(0, 0, 0);
    }
    if (controls) {
        controls.target = new THREE.Vector3();
    }
};

VIEWER.setLightsForCamera = function (camera) {
    var quaternion = new THREE.Quaternion();
    camera.updateMatrixWorld(true);
    quaternion.setFromRotationMatrix(camera.matrixWorld);
    VIEWER.directional_light1.position = VIEWER.light1_pos.clone().applyQuaternion(quaternion);
    VIEWER.directional_light2.position = VIEWER.light2_pos.clone().applyQuaternion(quaternion);
    VIEWER.directional_light3.position = VIEWER.light3_pos.clone().applyQuaternion(quaternion);
    VIEWER.directional_light4.position = VIEWER.light4_pos.clone().applyQuaternion(quaternion);
};

VIEWER.LoadModelJS = function (jspath) {
    var mesh = null;
    var loader = new THREE.JSONLoader();
    loader.load('', jspath, function (geometry, materials) {
        mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
        mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
        mesh.translation = THREE.GeometryUtils.center(geometry);
        VIEWER.scene.add(mesh);

        VIEWER.fitAll(VIEWER.scene);
    });

};

THREE.Color.prototype.multiply = function (a) {
    this.r *= a;
    this.g *= a;
    this.b *= a;
    return this;
}
VIEWER.getShaderColor = function (c, multiplier) {
    if (multiplier === undefined) {
        multiplier = 1.0;
    }
    if (c.length == 3) {
        c = c.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, function (m, r, g, b) {
            return r + r + g + g + b + b;
        });
    }
    return new THREE.Color(parseInt(c, 16)).multiply(multiplier);
}
VIEWER.getThreeFilter = function (id) {
    if (id == 'nearest') return THREE.NearestFilter;
    else return THREE.LinearFilter;
}
VIEWER.getThreeWrapping = function (id) {
    if (id == 'clamp') return THREE.ClampToEdgeWrapping;
    else return THREE.RepeatWrapping;
}
VIEWER.computeNormals = function (geo) {
    var v, vl, f, fl, face, vertices;
    console.log('init vertex arry');
    vertices = new Array(geo.vertices.length);
    for (v = 0, vl = geo.vertices.length; v < vl; v++) {
        vertices[v] = new THREE.Vector3();
    }
    console.log('add face normals');
    for (f = 0, fl = geo.faces.length; f < fl; f++) {
        face = geo.faces[f];
        if (face instanceof THREE.Face3) {
            vertices[face.a].add(face.normal);
            vertices[face.b].add(face.normal);
            vertices[face.c].add(face.normal);
        } else if (face instanceof THREE.Face4) {
            vertices[face.a].add(face.normal);
            vertices[face.b].add(face.normal);
            vertices[face.c].add(face.normal);
            vertices[face.d].add(face.normal);
        }
    }
    console.log('normalize');
    for (v = 0, vl = vertices.length; v < vl; v++) {
        vertices[v].normalize();
    }
    console.log('set face normals');
    for (f = 0, fl = geo.faces.length; f < fl; f++) {
        face = geo.faces[f];
        if (face instanceof THREE.Face3) {
            face.vertexNormals = [vertices[face.a], vertices[face.b], vertices[face.c]];
        } else if (face instanceof THREE.Face4) {
            face.vertexNormals = [vertices[face.a], vertices[face.b], vertices[face.c], vertices[face.d]];
        }
    }
};

VIEWER.geometry_freeMemory = function (geometry) {
    if (geometry && geometry.dispose) {
        geometry.dispose();
    }
};
VIEWER.scene_freeGeometry = function () {
    VIEWER.geometry_freeMemory(VIEWER.orig_geometry);
    delete VIEWER.orig_geometry;
    VIEWER.geometry_freeMemory(VIEWER.subd_geometry);
    delete VIEWER.subd_geometry;
    delete VIEWER.render_geometry;
};
VIEWER.threeMaterial_freeMemory = function (material) {
    if (material && material.dispose) {
        material.dispose();
    }
};
VIEWER.material_freeShadingModes = function (mat_index) {
    var mode_list = VIEWER.mat_shading_modes[mat_index];
    if (!mode_list) {
        return;
    }
    var modekeys = Object.keys(mode_list);
    for (var i = 0; i < modekeys.length; i++) {
        var key = modekeys[i];
        var mode = mode_list[key];
        VIEWER.threeMaterial_freeMemory(mode);
    }
};
VIEWER.material_validateType = function (name) {
    if (!name || !VIEWER.viewer_materials[name]) return "normal";
    return name;
};
VIEWER.material_getTexture = function (mat_idx, tex_id) {
    if (!tex_id) return false;
    if (VIEWER.mat_overrides[mat_idx] && (tex_id in VIEWER.mat_overrides[mat_idx].textures)) {
        return VIEWER.mat_overrides[mat_idx].textures[tex_id];
    }
    return false;
};
VIEWER.material_getFinal = function (mat_idx) {
    var mat = VIEWER.material_getInitialState(mat_idx);
    if (VIEWER.mat_overrides[mat_idx]) {
        for (var i in VIEWER.mat_overrides[mat_idx]) {
            if (i == "settings" || i == "textures")
                VIEWER.applyOverrides(mat[i], VIEWER.mat_overrides[mat_idx][i]);
            else
                mat[i] = VIEWER.mat_overrides[mat_idx][i];
        }
    }
    return mat;
};
VIEWER.material_getInitialState = function (mat_idx) {
    var mat = new Object();
    for (var i in VIEWER.materials[mat_idx]) {
        if (i != "settings" && i != "textures")
            mat[i] = VIEWER.materials[mat_idx][i];
    }
    mat.settings = VIEWER.material_getInitialSettings(mat_idx);
    mat.textures = new Object();
    return mat;
};
VIEWER.material_getInitialSettings = function (mat_idx) {
    var init_settings = new Object();
    VIEWER.applyOverrides(init_settings, VIEWER.default_material);
    if (VIEWER.materials[mat_idx])
        VIEWER.applyOverrides(init_settings, VIEWER.materials[mat_idx].settings);
    return init_settings;
};
VIEWER.material_getInitialTextures = function (mat_idx) {
    var init_textures = new Object();
    VIEWER.applyOverrides(init_textures, VIEWER.materials[mat_idx].textures);
    return init_textures;
};
VIEWER.applyOverrides = function (target, overrides) {
    if (!overrides) return;
    for (var i in overrides) {
        target[i] = overrides[i];
    }
};

VIEWER.model_freeShadingModes = function () {
    for (var i = 0; i < VIEWER.mat_shading_modes.length; i++) {
        VIEWER.material_freeShadingModes(i);
    }
    VIEWER.mat_shading_modes = [];
};
VIEWER.model_getNumberOfMaterials = function () {
    return VIEWER.materials.length;
};

var P3D = P3D || {};
P3D.ShaderChunk = {
    tangent_pars_fragment: ["#if defined(USE_TANGENTS)", "varying vec3 vTangent;", "varying vec3 vBinormal;", "#endif", ].join("\n"),
    tangent_pars_vertex: ["#if defined(USE_TANGENTS)", "attribute vec4 tangent;", "varying vec3 vTangent;", "varying vec3 vBinormal;", "#endif", ].join("\n"),
    tangent_vertex: ["#if defined(USE_TANGENTS)", "vTangent = normalMatrix * tangent.xyz;", "vBinormal = cross( vNormal, vTangent ) * tangent.w;", "#endif", ].join("\n"),
    normalmap_pars_fragment: ["#if defined(USE_NORMAL_MAP)", "uniform bool enableNormal;", "uniform sampler2D tNormal;", "uniform float uNormalScale;", "#endif", "#if defined(USE_NORMAL_MAP)", "vec3 applyTangentNormal( vec3 normal ) {", "vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;", "normalTex.xy *= uNormalScale;", "normalTex = normalize( normalTex );", "mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( normal ) );", "return (tsb * normalTex);", "}", "#endif", ].join("\n"),
    normalmap_fragment: ["#if defined(USE_NORMAL_MAP)", "if (enableNormal) normal = applyTangentNormal(normal);", "#endif", ].join("\n"),
    detail_normalmap_pars_fragment: ["#if defined(USE_DETAIL_NORMAL_MAP)", "uniform bool enableDetailNormal;", "uniform sampler2D tDetailNormal;", "uniform float uDetailNormalStrength;", "uniform float uDetailNormalRepeat;", "vec3 applyDetailTangentNormal( vec3 normal ) {", "vec3 normalTex = texture2D( tDetailNormal, vUv * uDetailNormalRepeat).xyz * 2.0 - 1.0;", "normalTex.xy *= uDetailNormalStrength;", "normalTex = normalize( normalTex );", "mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( normal ) );", "return (tsb * normalTex);", "}", "#endif", ].join("\n"),
    detail_normalmap_fragment: ["#if defined(USE_DETAIL_NORMAL_MAP)", "if (enableDetailNormal) normal = applyDetailTangentNormal(normal);", "#endif", ].join("\n"),
    bump_base_pars_fragment: ["#if defined(USE_BUMP_DERIVATIVES)", "#extension GL_OES_standard_derivatives : enable", "vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {", "vec3 vSigmaX = dFdx( surf_pos );", "vec3 vSigmaY = dFdy( surf_pos );", "vec3 vN = surf_norm;", "vec3 R1 = cross( vSigmaY, vN );", "vec3 R2 = cross( vN, vSigmaX );", "float fDet = dot( vSigmaX, R1 );", "vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );", "return normalize( abs( fDet ) * surf_norm - vGrad );", "}", "#endif", ].join("\n"),
    bumpmap_pars_fragment: ["#ifdef USE_BUMPMAP", "uniform bool enableBump;", "uniform sampler2D bumpMap;", "uniform float bumpScale;", "vec2 dHdxy_fwd() {", "vec2 dSTdx = dFdx( vUv );", "vec2 dSTdy = dFdy( vUv );", "float Hll = bumpScale * texture2D( bumpMap, vUv ).x;", "float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;", "float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;", "return vec2( dBx, dBy );", "}", "#endif", ].join("\n"),
    bumpmap_fragment: ["#ifdef USE_BUMPMAP", "if (enableBump) normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );", "#endif", ].join("\n"),
    detail_bumpmap_pars_fragment: ["#ifdef USE_DETAIL_BUMPMAP", "uniform bool enableDetailBump;", "uniform sampler2D detailBumpMap;", "uniform float uDetailBumpStrength;", "uniform float uDetailBumpRepeat;", "vec2 detail_dHdxy_fwd() {", "vec2 dSTdx = dFdx( vUv );", "vec2 dSTdy = dFdy( vUv );", "float Hll = uDetailBumpStrength * texture2D( detailBumpMap, vUv * uDetailBumpRepeat ).x;", "float dBx = uDetailBumpStrength * texture2D( detailBumpMap, (vUv + dSTdx) * uDetailBumpRepeat ).x - Hll;", "float dBy = uDetailBumpStrength * texture2D( detailBumpMap, (vUv + dSTdy) * uDetailBumpRepeat ).x - Hll;", "return vec2( dBx, dBy );", "}", "#endif", ].join("\n"),
    detail_bumpmap_fragment: ["#ifdef USE_DETAIL_BUMPMAP", "if (enableDetailBump) normal = perturbNormalArb( -vViewPosition, normal, detail_dHdxy_fwd() );", "#endif", ].join("\n"),
    ao_pars_fragment: ["#ifdef USE_AMBIENT_OCCLUSION", "uniform vec3 uAOColor;", "uniform bool enableAO;", "uniform sampler2D tAO;", "uniform float uAOStrength;", "#endif", ].join("\n"),
    ao_fragment: ["#ifdef USE_AMBIENT_OCCLUSION", "if( enableAO ) {", "#ifdef GAMMA_INPUT", "vec3 aoColor = texture2D( tAO, vUv ).xyz * uAOColor;", "aoColor.xyz *= aoColor.xyz;", "gl_FragColor.xyz = gl_FragColor.xyz * mix(vec3(1, 1, 1), aoColor.xyz, uAOStrength);", "#else", "gl_FragColor.xyz = gl_FragColor.xyz * mix(vec3(1, 1, 1), texture2D( tAO, vUv ).xyz * uAOColor.xyz, uAOStrength);", "#endif", "}", "#endif", ].join("\n"),
    glow_pars_fragment: ["#ifdef USE_GLOW", "uniform bool enableGlow;", "uniform sampler2D tGlow;", "uniform float uGlowStrength;", "uniform vec3 uGlowColor;", "uniform float uGlowDiffuseInfluence;", "#endif", ].join("\n"),
    glow_fragment: ["#ifdef USE_GLOW", "if( enableGlow ) {", "vec3 glowColor = texture2D( tGlow, vUv ).xyz;", "#ifdef GAMMA_INPUT", "glowColor.xyz *= glowColor.xyz;", "#endif", "gl_FragColor.xyz = gl_FragColor.xyz + (mix(vec3(1,1,1), diffuseColor.xyz, uGlowDiffuseInfluence) * glowColor.xyz * uGlowColor.xyz * uGlowStrength);", "}", "#endif", ].join("\n"),
    reflection_pars_fragment: ["#ifdef USE_REFLECTION", "uniform bool enableReflection;", "uniform sampler2D tReflection;", "uniform float uReflectionStrength;", "uniform vec3 uReflectionColor;", "uniform float uReflectionCoefficient;", "#ifdef USE_REFLECTION_MASK", "uniform bool enableReflectionMask;", "uniform sampler2D tReflectionMask;", "uniform float uReflectionMaskStrength;", "#endif", "#endif", ].join("\n"),
    reflection_fragment: ["#ifdef USE_REFLECTION", "if( enableReflection ) {", "vec3 viewNormalized = normalize( -vViewPosition );", "vec3 r = reflect(viewNormalized, normal);", "vec2 texco;", "float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) );", "texco.x = r.x/m + 0.5;", "texco.y = r.y/m + 0.5;", "vec3 texelColor = texture2D( tReflection, texco ).xyz * uReflectionColor.xyz;", "#ifdef GAMMA_INPUT", "texelColor *= texelColor;", "#endif", "float fresnel = pow(1.0 + dot(viewNormalized, normal), uReflectionCoefficient);", "#ifdef USE_REFLECTION_MASK", "if( enableReflectionMask ) {", "texelColor = texelColor * mix(vec3(1.0), texture2D( tReflectionMask, vUv ).xyz, uReflectionMaskStrength);", "}", "#endif", "gl_FragColor.xyz = vec3(1.0) - (vec3(1.0) - gl_FragColor.xyz * gl_FragColor.a) * (vec3(1.0) - texelColor * uReflectionStrength * fresnel);", "gl_FragColor.a = 1.0 - (1.0 - gl_FragColor.a) * (1.0 - uReflectionStrength * fresnel);", "}", "#endif", ].join("\n"),
    uv_pars_vertex: ["varying vec2 vUv;", "uniform vec2 uOffset;", "uniform vec2 uRepeat;", ].join("\n"),
    uv_vertex: ["vUv = vec2(uv.x, 1.0 - uv.y) * uRepeat + uOffset;", ].join("\n"),
    uv_pars_fragment: ["varying vec2 vUv;", ].join("\n"),
    diffuse_pars_fragment: ["uniform vec3 uDiffuseColor;", "uniform float uDiffuseAlpha;", "uniform float uAlphaClip;", "#ifdef USE_DIFFUSE_TEXTURE", "uniform bool enableDiffuse;", "uniform sampler2D tDiffuse;", "#endif", ].join("\n"),
    diffuse_fragment: ["gl_FragColor.xyz *= uDiffuseColor;", "gl_FragColor.a *= uDiffuseAlpha;", "vec4 diffuseColor = gl_FragColor;", "#ifdef USE_DIFFUSE_TEXTURE", "if( enableDiffuse ) {", "diffuseColor = texture2D( tDiffuse, vUv );", "if (diffuseColor.a < uAlphaClip) discard;", "#ifdef GAMMA_INPUT", "vec4 texelColor = diffuseColor;", "texelColor.xyz *= texelColor.xyz;", "gl_FragColor = gl_FragColor * texelColor;", "#else", "gl_FragColor = gl_FragColor * diffuseColor;", "#endif", "}", "#endif", ].join("\n"),
    detail_diffuse_pars_fragment: ["#ifdef USE_DETAIL_DIFFUSE_TEXTURE", "uniform float uDetailDiffuseStrength;", "uniform float uDetailDiffuseRepeat;", "uniform bool enableDetailDiffuse;", "uniform sampler2D tDetailDiffuse;", "#endif", ].join("\n"),
    detail_diffuse_fragment: ["#ifdef USE_DETAIL_DIFFUSE_TEXTURE", "if( enableDetailDiffuse ) {", "vec4 texelDetailColor = texture2D( tDetailDiffuse, vUv * uDetailDiffuseRepeat );", "#ifdef GAMMA_INPUT", "texelDetailColor.xyz *= texelDetailColor.xyz;", "#endif", "texelDetailColor.xyz *= 2.0;", "gl_FragColor.xyz = gl_FragColor.xyz * mix(vec3(1, 1, 1), texelDetailColor.xyz, uDetailDiffuseStrength);", "}", "#endif", ].join("\n"),
    directionlight_pars_fragment: ["#if MAX_DIR_LIGHTS > 0", "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];", "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];", "#endif", ].join("\n"),
    directionlight_fragment: ["#if MAX_DIR_LIGHTS > 0", "vec3 dirDiffuse = vec3( 0.0 );", "vec3 dirSpecular = vec3( 0.0 );", "for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {", "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", "vec3 dirVector = normalize( lDirection.xyz );", "float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );", "dirDiffuse += directionalLightColor[ i ] * dirDiffuseWeight;", "vec3 dirHalfVector = normalize( dirVector + viewPosition );", "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );", "vec3 dirSpecularWeight = specularTex * max( pow( dirDotNormalHalf, specShininess + 0.0001 ), 0.0 );", "#ifdef PHYSICALLY_BASED_SHADING", "float specularNormalization = ( specShininess + 2.0001 ) / 8.0;", "vec3 schlick = uSpecularColor + uSpecularColor * vec3( 1.0 - uSpecularColor.r ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );", "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;", "#else", "dirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;", "#endif", "}", "gl_FragColor.xyz = gl_FragColor.xyz * ( dirDiffuse + ambientLightColor * uAmbientColor) + dirSpecular;", "#endif", ].join("\n"),
    hslshift_pars_fragment: ["uniform float uHueShift;", "uniform float uSaturationShift;", "uniform float uLightnessShift;", "vec3 RGBtoHSL( vec3 color) {", "float var_Min = min(color.x, min(color.y, color.z));", "float var_Max = max(color.x, max(color.y, color.z));", "float del_Max = var_Max - var_Min;", "float L = ( var_Max + var_Min ) / 2.0;", "if ( del_Max == 0.0 )", "{", "return vec3(0.0, 0.0, L);", "}", "else", "{", "float S = 0.0;", "float H = 0.0;", "if ( L < 0.5 ) S = del_Max / ( var_Max + var_Min );", "else		   S = del_Max / ( 2.0 - var_Max - var_Min );", "float del_R = ( ( ( var_Max - color.x ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;", "float del_G = ( ( ( var_Max - color.y ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;", "float del_B = ( ( ( var_Max - color.z ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;", "if	  ( color.x == var_Max ) H = del_B - del_G;", "else if ( color.y == var_Max ) H = ( 1.0 / 3.0 ) + del_R - del_B;", "else if ( color.z == var_Max ) H = ( 2.0 / 3.0 ) + del_G - del_R;", "if ( H < 0.0 ) H += 1.0;", "if ( H > 1.0 ) H -= 1.0;", "return vec3(H, S, L);", "}", "}", "float Hue_2_RGB( float v1, float v2, float vH ) {", "if ( vH < 0.0 ) vH += 1.0;", "if ( vH > 1.0 ) vH -= 1.0;", "if ( ( 6.0 * vH ) < 1.0 ) return ( v1 + ( v2 - v1 ) * 6.0 * vH );", "if ( ( 2.0 * vH ) < 1.0 ) return ( v2 );", "if ( ( 3.0 * vH ) < 2.0 ) return ( v1 + ( v2 - v1 ) * ( ( 2.0 / 3.0 ) - vH ) * 6.0 );", "return v1;", "}", "vec3 HSLtoRGB( vec3 color) {", "if ( color.y == 0.0 ) {", "return vec3(color.z);", "}", "else {", "float var_2 = 0.0;", "if ( color.z < 0.5 ) var_2 = color.z * ( 1.0 + color.y );", "else		   var_2 = ( color.z + color.y ) - ( color.y * color.z );", "float var_1 = 2.0 * color.z - var_2;", "return vec3(Hue_2_RGB(var_1, var_2, color.x+(1.0/3.0)), Hue_2_RGB(var_1, var_2, color.x), Hue_2_RGB(var_1, var_2, color.x - (1.0 / 3.0)));", "}", "}", "vec3 applyHSLShift( vec3 color, float hue, float saturation, float lightness ) {", "vec3 hsl = RGBtoHSL(color);", "hsl.x = hsl.x + hue;", "if (hsl.x<0.0) hsl.x = hsl.x + 1.0;", "if (hsl.x>1.0) hsl.x = hsl.x - 1.0;", "hsl.y = hsl.y + saturation;", "if (hsl.y<0.0) hsl.y = 0.0;", "if (hsl.y>1.0) hsl.y = 1.0;", "hsl.z = hsl.z + lightness;", "if (hsl.z<0.0) hsl.z = 0.0;", "if (hsl.z>1.0) hsl.z = 1.0;", "return HSLtoRGB(hsl);", "}", ].join("\n"),
    position_vertex: ["vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", "vViewPosition = -mvPosition.xyz;", "gl_Position = projectionMatrix * mvPosition;", ].join("\n"),
    colormultiply_pars_fragment: ["uniform float uMultiply;", ].join("\n"),
    colormultiply_fragment: ["gl_FragColor.xyz *= uMultiply;", ].join("\n"),
};
P3D.UniformsLib = {
    ao: {
        "enableAO": {
            type: "i",
            value: 0
        },
        "tAO": {
            type: "t",
            value: null
        },
        "uAOStrength": {
            type: "f",
            value: 1.0
        },
        "uAOColor": {
            type: "c",
            value: new THREE.Color(0xffffff)
        },
    },
    glow: {
        "enableGlow": {
            type: "i",
            value: 0
        },
        "tGlow": {
            type: "t",
            value: null
        },
        "uGlowStrength": {
            type: "f",
            value: 1.0
        },
        "uGlowColor": {
            type: "c",
            value: new THREE.Color(0xffffff)
        },
        "uGlowDiffuseInfluence": {
            type: "f",
            value: 0.0
        },
    },
    reflection: {
        "enableReflection": {
            type: "i",
            value: 0
        },
        "enableReflectionMask": {
            type: "i",
            value: 0
        },
        "tReflection": {
            type: "t",
            value: null
        },
        "tReflectionMask": {
            type: "t",
            value: null
        },
        "uReflectionStrength": {
            type: "f",
            value: 0.5
        },
        "uReflectionMaskStrength": {
            type: "f",
            value: 1.0
        },
        "uReflectionColor": {
            type: "c",
            value: new THREE.Color(0xffffff)
        },
        "uReflectionCoefficient": {
            type: "f",
            value: 0.0
        },
    },
    diffuse: {
        "enableDiffuse": {
            type: "i",
            value: 0
        },
        "tDiffuse": {
            type: "t",
            value: null
        },
        "uDiffuseColor": {
            type: "c",
            value: new THREE.Color(0xffffff)
        },
        "uDiffuseAlpha": {
            type: "f",
            value: 1.0
        },
        "uAlphaClip": {
            type: "f",
            value: 0
        },
    },
    detail_diffuse: {
        "enableDetailDiffuse": {
            type: "i",
            value: 0
        },
        "tDetailDiffuse": {
            type: "t",
            value: null
        },
        "uDetailDiffuseStrength": {
            type: "f",
            value: 1.0
        },
        "uDetailDiffuseRepeat": {
            type: "f",
            value: 1.0
        },
    },
    uv: {
        "uOffset": {
            type: "v2",
            value: new THREE.Vector2(0, 0)
        },
        "uRepeat": {
            type: "v2",
            value: new THREE.Vector2(1, 1)
        },
    },
    bump: THREE.UniformsUtils.merge([THREE.UniformsLib["bump"], {
        "enableBump": {
            type: "i",
            value: 0
        },
    }]),
    detail_bump: {
        "enableDetailBump": {
            type: "i",
            value: 0
        },
        "detailBumpMap": {
            type: "t",
            value: null
        },
        "uDetailBumpStrength": {
            type: "f",
            value: 1.0
        },
        "uDetailBumpRepeat": {
            type: "f",
            value: 1.0
        },
    },
    normalmap: {
        "enableNormal": {
            type: "i",
            value: 0
        },
        "tNormal": {
            type: "t",
            value: null
        },
        "uNormalScale": {
            type: "f",
            value: 1.0
        },
    },
    detail_normalmap: {
        "enableDetailNormal": {
            type: "i",
            value: 0
        },
        "tDetailNormal": {
            type: "t",
            value: null
        },
        "uDetailNormalStrength": {
            type: "f",
            value: 1.0
        },
        "uDetailNormalRepeat": {
            type: "f",
            value: 1.0
        },
    },
    hslshift: {
        "uHueShift": {
            type: "f",
            value: 0.0
        },
        "uSaturationShift": {
            type: "f",
            value: 0.0
        },
        "uLightnessShift": {
            type: "f",
            value: 0.0
        },
    },
    colormultiply: {
        "uMultiply": {
            type: "f",
            value: 1.0
        },
    },
};
P3D.Shaders = {
    'normal': {
        uniforms: THREE.UniformsUtils.merge([THREE.UniformsLib["lights"], P3D.UniformsLib["ao"], P3D.UniformsLib["glow"], P3D.UniformsLib["reflection"], P3D.UniformsLib["uv"], P3D.UniformsLib["diffuse"], P3D.UniformsLib["detail_diffuse"], P3D.UniformsLib["bump"], P3D.UniformsLib["detail_bump"], P3D.UniformsLib["normalmap"], P3D.UniformsLib["detail_normalmap"], P3D.UniformsLib["colormultiply"], {
            "enableSpecular": {
                type: "i",
                value: 0
            },
            "enableSpecularShine": {
                type: "i",
                value: 0
            },
            "tSpecular": {
                type: "t",
                value: 1,
                texture: null
            },
            "tSpecularShine": {
                type: "t",
                value: 2,
                texture: null
            },
            "uSpecularColor": {
                type: "c",
                value: new THREE.Color(0x111111)
            },
            "uAmbientColor": {
                type: "c",
                value: new THREE.Color(0xffffff)
            },
            "uShininess": {
                type: "f",
                value: 30
            },
        }]),
        fragmentShader: ["uniform vec3 uAmbientColor;", "uniform vec3 uSpecularColor;", "uniform float uShininess;", "#ifdef USE_SPEC_TEX", "uniform sampler2D tSpecular;", "uniform bool enableSpecular;", "#endif", "#ifdef USE_SPEC_SHINE_TEX", "uniform sampler2D tSpecularShine;", "uniform bool enableSpecularShine;", "#endif", "varying vec3 vNormal;", "uniform vec3 ambientLightColor;", "varying vec3 vViewPosition;", P3D.ShaderChunk["uv_pars_fragment"], P3D.ShaderChunk["bump_base_pars_fragment"], P3D.ShaderChunk["bumpmap_pars_fragment"], P3D.ShaderChunk["detail_bumpmap_pars_fragment"], P3D.ShaderChunk["ao_pars_fragment"], P3D.ShaderChunk["glow_pars_fragment"], P3D.ShaderChunk["reflection_pars_fragment"], P3D.ShaderChunk["diffuse_pars_fragment"], P3D.ShaderChunk["detail_diffuse_pars_fragment"], P3D.ShaderChunk["directionlight_pars_fragment"], P3D.ShaderChunk["tangent_pars_fragment"], P3D.ShaderChunk["normalmap_pars_fragment"], P3D.ShaderChunk["detail_normalmap_pars_fragment"], P3D.ShaderChunk["colormultiply_pars_fragment"], "void main() {", "gl_FragColor = vec4( 1.0 );", "vec3 normal = vNormal;", P3D.ShaderChunk["diffuse_fragment"], P3D.ShaderChunk["detail_diffuse_fragment"], P3D.ShaderChunk["ao_fragment"], "vec3 specularTex = vec3( 1.0 );", "#ifdef USE_SPEC_TEX", "if( enableSpecular )", "specularTex = texture2D( tSpecular, vUv ).xyz;", "#endif", "float specShininess = uShininess;", "#ifdef USE_SPEC_SHINE_TEX", "if (enableSpecularShine) specShininess *= texture2D( tSpecularShine, vUv ).x;", "#endif", P3D.ShaderChunk["bumpmap_fragment"], P3D.ShaderChunk["detail_bumpmap_fragment"], P3D.ShaderChunk["normalmap_fragment"], P3D.ShaderChunk["detail_normalmap_fragment"], "normal = normalize( normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) ) );", "vec3 viewPosition = normalize( vViewPosition );", P3D.ShaderChunk["directionlight_fragment"], P3D.ShaderChunk["glow_fragment"], P3D.ShaderChunk["reflection_fragment"], P3D.ShaderChunk["colormultiply_fragment"], THREE.ShaderChunk["linear_to_gamma_fragment"], "}"].join("\n"),
        vertexShader: ["varying vec3 vNormal;", "varying vec3 vViewPosition;", P3D.ShaderChunk["uv_pars_vertex"], P3D.ShaderChunk["tangent_pars_vertex"], "void main() {", P3D.ShaderChunk["position_vertex"], "vNormal = normalMatrix * normal;", P3D.ShaderChunk["tangent_vertex"], P3D.ShaderChunk["uv_vertex"], "}"].join("\n")
    },
    'shadesphere': {
        uniforms: THREE.UniformsUtils.merge([P3D.UniformsLib["bump"], P3D.UniformsLib["detail_bump"], P3D.UniformsLib["normalmap"], P3D.UniformsLib["detail_normalmap"], P3D.UniformsLib["hslshift"], P3D.UniformsLib["colormultiply"], {
            "enableShadesphere": {
                type: "i",
                value: 0
            },
            "tShadesphere": {
                type: "t",
                value: 0,
                texture: null
            },
            "uShadesphereColor": {
                type: "c",
                value: new THREE.Color(0xffffff)
            },
        }]),
        fragmentShader: ["uniform vec3 uShadesphereColor;", "uniform bool enableShadesphere;", "uniform sampler2D tShadesphere;", "varying vec3 vNormal;", "varying vec3 vViewPosition;", P3D.ShaderChunk["uv_pars_fragment"], P3D.ShaderChunk["bump_base_pars_fragment"], P3D.ShaderChunk["bumpmap_pars_fragment"], P3D.ShaderChunk["detail_bumpmap_pars_fragment"], P3D.ShaderChunk["tangent_pars_fragment"], P3D.ShaderChunk["normalmap_pars_fragment"], P3D.ShaderChunk["detail_normalmap_pars_fragment"], P3D.ShaderChunk["hslshift_pars_fragment"], P3D.ShaderChunk["colormultiply_pars_fragment"], "void main() {", "gl_FragColor = vec4( vec3(0.5), 1.0 );", "vec3 normal = vNormal;", P3D.ShaderChunk["bumpmap_fragment"], P3D.ShaderChunk["detail_bumpmap_fragment"], P3D.ShaderChunk["normalmap_fragment"], P3D.ShaderChunk["detail_normalmap_fragment"], "normal = normalize( normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) ) );", "vec3 viewPosition = normalize( vViewPosition );", "if( enableShadesphere ) {", "vec2 texco;", "texco.x = 0.5 + 0.49 * normal.x;", "texco.y = 0.5 + 0.49 * normal.y;", "#ifdef GAMMA_INPUT", "vec3 texelColor = texture2D( tShadesphere, texco ).xyz;", "texelColor *= texelColor;", "gl_FragColor.xyz = texelColor;", "#else", "gl_FragColor.xyz = texture2D( tShadesphere, texco ).xyz;", "#endif", "}", "gl_FragColor.xyz *= uShadesphereColor;", P3D.ShaderChunk["colormultiply_fragment"], THREE.ShaderChunk["linear_to_gamma_fragment"], "if (uHueShift != 0.0 || uSaturationShift != 0.0 || uLightnessShift != 0.0) gl_FragColor.xyz = applyHSLShift(gl_FragColor.xyz, uHueShift, uSaturationShift, uLightnessShift);", "}"].join("\n"),
        vertexShader: ["varying vec3 vNormal;", "varying vec2 vUv;", "varying vec3 vViewPosition;", P3D.ShaderChunk["tangent_pars_vertex"], "void main() {", P3D.ShaderChunk["position_vertex"], "vNormal = normalMatrix * normal;", P3D.ShaderChunk["tangent_vertex"], "vUv = vec2(uv.x, 1.0 - uv.y);", "}"].join("\n")
    },
    'shadeless': {
        uniforms: THREE.UniformsUtils.merge([P3D.UniformsLib["diffuse"], P3D.UniformsLib["detail_diffuse"], P3D.UniformsLib["uv"], P3D.UniformsLib["colormultiply"], ]),
        vertexShader: [P3D.ShaderChunk["uv_pars_vertex"], "varying vec3 vViewPosition;", "void main() {", P3D.ShaderChunk["position_vertex"], P3D.ShaderChunk["uv_vertex"], "}"].join("\n"),
        fragmentShader: [P3D.ShaderChunk["uv_pars_fragment"], P3D.ShaderChunk["diffuse_pars_fragment"], P3D.ShaderChunk["detail_diffuse_pars_fragment"], P3D.ShaderChunk["colormultiply_pars_fragment"], "void main() {", "gl_FragColor = vec4( 1.0 );", P3D.ShaderChunk["diffuse_fragment"], P3D.ShaderChunk["detail_diffuse_fragment"], P3D.ShaderChunk["colormultiply_fragment"], THREE.ShaderChunk["linear_to_gamma_fragment"], "}"].join("\n")
    },
    'xray': {
        uniforms: THREE.UniformsUtils.merge([P3D.UniformsLib["colormultiply"], {
            "uDiffuseColor": {
                type: "c",
                value: new THREE.Color(0xffffff)
            },
        }]),
        vertexShader: ["varying vec3 vViewPosition;", "varying float vAlpha;", "void main() {", P3D.ShaderChunk["position_vertex"], "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", "vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );", "vec3 I = worldPosition.xyz - cameraPosition;", "vAlpha = 0.04 + (1.0 - pow( abs(dot( normalize( I ), worldNormal )), 0.2 ));", "}"].join("\n"),
        fragmentShader: [P3D.ShaderChunk["diffuse_pars_fragment"], P3D.ShaderChunk["colormultiply_pars_fragment"], "varying float vAlpha;", "void main() {", "gl_FragColor = vec4( uDiffuseColor, vAlpha );", P3D.ShaderChunk["colormultiply_fragment"], "}"].join("\n")
    },
};

VIEWER.shadingMode_textureDisabled = function (mode_def, tex_type) {
    return (mode_def.disable_textures && (mode_def.disable_textures.indexOf(tex_type) != -1));
};
VIEWER.shadingMode_hasTexture = function (mode_def, mat_idx, tex_type) {
    return (VIEWER.shadingMode_textureDisabled(mode_def, tex_type)) ? false : VIEWER.material_getTexture(mat_idx, tex_type);
};

VIEWER.shader_updateUniform = function (key, uniforms, s, mat_idx, shader_def) {
    var item = VIEWER.shader_uniform_mapping[key];
    if (item && (key in uniforms)) {
        if (shader_def.force_settings) {
            s = $.extend({}, s, shader_def.force_settings);
        }
        var uniform = uniforms[key];
        if (item.mapping == 'float') {
            uniform.value = s[item.bindings[0]];
        } else if (item.mapping == 'color') {
            uniform.value = VIEWER.getShaderColor(s[item.bindings[0]]);
        } else if (typeof (item.mapping) == "function") {
            uniform.value = item.mapping(s, mat_idx, shader_def);
        }
    }
}
VIEWER.shader_applySettings = function (uniforms, s, mat_idx, shader_def) {
    var keys = Object.keys(uniforms);
    for (var i = 0; i < keys.length; i++) {
        VIEWER.shader_updateUniform(keys[i], uniforms, s, mat_idx, shader_def);
    }
}
VIEWER.textureLoadCallback = function () {
    if (VIEWER.callbacks.updateViewerPreviews) {
        VIEWER.callbacks.updateViewerPreviews();
    }
}
VIEWER._textureCache = new Object();
VIEWER.textureCache_cleanup = function () {
    var cached = Object.keys(VIEWER._textureCache);
    var urls = new Object();
    for (var i = 0; i < VIEWER.model_getNumberOfMaterials(); i++) {
        var mats = [VIEWER.materials[i], VIEWER.mat_overrides[i]];
        for (var j = 0; j < 2; j++) {
            var mat = mats[j];
            if (mat) {
                var keys = Object.keys(mat.textures);
                for (var k = 0; k < keys.length; k++) {
                    var cache_id = mat.textures[keys[k]];
                    var tex_settings = VIEWER.shader_texture_settings[keys[k]];
                    if (tex_settings && tex_settings.skip_filter)
                        cache_id += '_skip_filter';
                    urls[cache_id] = true;
                }
            }
        }
    }
    urls = Object.keys(urls);
    for (var i = 0; i < cached.length; i++) {
        var url = cached[i];
        if (urls.indexOf(url) == -1) {
            VIEWER.texture_freeMemory(VIEWER._textureCache[url]);
            delete VIEWER._textureCache[url];
        }
    }
};
VIEWER.textureCache_set = function (url, texture) {
    VIEWER._textureCache[url] = texture;
}
VIEWER.textureCache_load = function (url) {
    return (VIEWER._textureCache[url]) ? VIEWER._textureCache[url] : false;
};

VIEWER.texture_freeMemory = function (texture) {
    if (texture && texture.dispose) {
        texture.dispose();
    }
};

VIEWER.setUniforms = function (mat_idx, list_shaders, mat_settings, mat_textures, mat_shading_modes) {
    var active_material = VIEWER.material_validateType(mat_settings.material_type);
    for (var i = 0; i < list_shaders.length; ++i) {
        var item = list_shaders[i];
        var item_shader = VIEWER.viewer_materials[active_material].shader;
        if (item.force_shader) {
            item_shader = item.force_shader;
        }
        item.shader = P3D.Shaders[item_shader];
        item.shader_name = item_shader;
        item.uniforms = THREE.UniformsUtils.clone(item.shader.uniforms);
        item.fragHeaders = [];
        item.vertHeaders = [];
        VIEWER.shader_applySettings(item.uniforms, mat_settings, mat_idx, item);
        if (item.mat_types && item.mat_types[active_material]) {
            var s = item.mat_types[active_material];
            if (s.force_settings) {
                var settings = $.extend({}, mat_settings, s.force_settings);
                VIEWER.shader_applySettings(item.uniforms, settings, mat_idx, item);
            }
        }
    }
    var active_textures = 0;
    var trev = (mat_textures) ? VIEWER.loadStates.resetTextures(mat_idx) : 0;
    VIEWER.textureCache_cleanup();
    var uniform_fillTexture = function (uniform, texture, skip_filter) {
        uniform.value = texture;
        uniform.value.wrapS = uniform.value.wrapT = VIEWER.getThreeWrapping(mat_settings.tex_wrap);
        uniform.value.anisotropy = (skip_filter || mat_settings.tex_filter != 'nearest') ? VIEWER.renderer.getMaxAnisotropy() : 1;
        if (!skip_filter) {
            uniform.value.magFilter = VIEWER.getThreeFilter(mat_settings.tex_filter);
        }
    };
    var shader_processTexture = function (texture, type, url) {
        var s = VIEWER.shader_texture_settings[type];
        if (VIEWER.loadStates.textureSetReady(url, mat_idx, trev)) {
            for (var i = 0; i < list_shaders.length; i++) {
                var item = list_shaders[i];
                if (VIEWER.shadingMode_textureDisabled(item, type) || !(s.param in item.uniforms))
                    continue;
                uniform_fillTexture(item.uniforms[s.param], texture, s.skip_filter);
                if (s.uniform_callback)
                    s.uniform_callback(item.uniforms, mat_settings);
                if (s.enable_flag)
                    item.uniforms[s.enable_flag].value = true;
                VIEWER.textureLoadCallback();
            }
            if (s.generic_callback)
                s.generic_callback(texture, mat_shading_modes);
            VIEWER.force_render_frames = 4;
        }
    }
    var shader_loadTexture = function (type) {
        var s = VIEWER.shader_texture_settings[type];
        if (VIEWER.disabled_textures[type])
            return;
        if (s && mat_textures && mat_textures[type] && VIEWER.viewer_materials[active_material].textures_list.indexOf(type) !== -1) {
            var url = mat_textures[type];
            if (active_textures >= VIEWER.maximum_textures)
                return;
            active_textures++;
            VIEWER.loadStates.textureSetLoading(url, mat_idx, trev);
            var cache_id = url;
            if (s.skip_filter)
                cache_id += '_skip_filter';
            var texture;
            if (texture = VIEWER.textureCache_load(cache_id)) {
                texture.needsUpdate = true;
                shader_processTexture(texture, type, url);
            } else {
                THREE.ImageUtils.loadTexture(url, {}, function (texture) {
                    if (VIEWER.textureCache_load(cache_id)) {
                        texture = VIEWER.textureCache_load(cache_id);
                    }
                    VIEWER.textureCache_set(cache_id, texture);
                    shader_processTexture(texture, type, url);
                });
            }
            s.defines.forEach(function (define) {
                var def = "#define " + define.d;
                for (var i = 0; i < list_shaders.length; i++) {
                    var item = list_shaders[i];
                    if (VIEWER.shadingMode_textureDisabled(item, type) || !(s.param in item.uniforms))
                        continue;
                    if (define.frag && item.fragHeaders.indexOf(def) == -1) item.fragHeaders.push(def);
                    if (define.vert && item.vertHeaders.indexOf(def) == -1) item.vertHeaders.push(def);
                }
            });
        }
    }
    var textures = Object.keys(mat_textures);
    textures.sort(function (a, b) {
        var a_priority = VIEWER.texture_priority_order.indexOf(a);
        var b_priority = VIEWER.texture_priority_order.indexOf(b);
        if (a_priority == -1)
            a_priority = VIEWER.texture_priority_order.length;
        if (b_priority == -1)
            b_priority = VIEWER.texture_priority_order.length;
        if (a_priority > b_priority)
            return 1;
        if (a_priority < b_priority)
            return -1;
        return 0;
    });
    for (var i = 0; i < textures.length; i++) {
        shader_loadTexture(textures[i]);
    }
    for (var i = 0; i < list_shaders.length; ++i) {
        var item = list_shaders[i];
        item.vertShader = item.vertHeaders.join('\n') + '\n' + item.shader.vertexShader;
        item.fragShader = item.fragHeaders.join('\n') + '\n' + item.shader.fragmentShader;
    }
}

VIEWER.threeMaterial_getParameter = function (shader_def, key, mat_settings) {
    if (shader_def.mat_types) {
        var active_material = VIEWER.material_validateType(mat_settings.material_type);
        if (shader_def.mat_types[active_material]) {
            var s = shader_def.mat_types[active_material];
            if (s.force_settings) {
                mat_settings = $.extend({}, mat_settings, s.force_settings);
            }
        }
    }
    var item = shader_def[key];
    if (typeof (item) == "function") {
        return item(mat_settings);
    } else {
        return item;
    }
}
VIEWER.threeMaterial_applySettings = function (base_mat, wireon_mat, shader_def, mat_settings) {
    var material_settings = {};
    var wireon_settings = {};
    if (shader_def.color) {
        material_settings.color = VIEWER.threeMaterial_getParameter(shader_def, 'color', mat_settings);
    }
    if (shader_def.opacity) {
        material_settings.opacity = VIEWER.threeMaterial_getParameter(shader_def, 'opacity', mat_settings);
        material_settings.transparent = true;
    }
    if (shader_def.wireonname) {
        if (shader_def.wirecolor) {
            wireon_settings.color = VIEWER.threeMaterial_getParameter(shader_def, 'wirecolor', mat_settings);
        }
    }
    if (material_settings) {
        var mat = $.isArray(base_mat) ? base_mat[1] : base_mat;
        mat.setValues(material_settings);
    }
    if (wireon_settings && wireon_mat) {
        wireon_mat[1].setValues(wireon_settings);
    }
};

VIEWER.recreateMaterials = function () {
    VIEWER.model_freeShadingModes();
    for (var i = 0; i < VIEWER.model_getNumberOfMaterials(); i++) {
        VIEWER.recreateMaterial(i);
    }
};
VIEWER.recreateMaterial = function (index) {
    var mat_shading_modes = new Object();
    var material = VIEWER.material_getFinal(index);
    var mat_textures = material.textures;
    var mat_settings = material.settings;
    var material_type = VIEWER.material_validateType(mat_settings.material_type);
    var force_settings = VIEWER.viewer_materials[material_type].force_settings;
    if (force_settings) {
        mat_settings = $.extend({}, mat_settings, force_settings);
    }
    var shaderkeys = VIEWER.viewer_shading_mode_keys;
    var createMaterial = function (type, settings) {
        if (type == 'basic')
            return new THREE.MeshBasicMaterial(settings);
        else {
            if (type == 'wire') {
                settings.wireframe = true;
                settings.wireframeLinewidth = 1;
            }
            return new THREE.MeshLambertMaterial(settings);
        }
    }
    for (var i = 0; i < shaderkeys.length; i++) {
        var key = shaderkeys[i];
        var mode_def = VIEWER.viewer_shading_modes[key];
        var base_mat = createMaterial(mode_def.type, {});
        var wireon_mat;
        if (mode_def.wireonname) {
            wireon_mat = [base_mat, createMaterial('wire', {
                color: 0x888888,
                shading: THREE.FlatShading,
                wireframe: true,
                wireframeLinewidth: 1,
                transparent: true
            })];
            mat_shading_modes[mode_def.wireonname] = wireon_mat;
        }
        if (mode_def.fix_opacity) {
            base_mat = [new THREE.MeshBasicMaterial({
                color: 0x000000,
                transparent: true,
                opacity: 0.0
            }), base_mat];
        }
        mat_shading_modes[key] = base_mat;
        VIEWER.threeMaterial_applySettings(base_mat, wireon_mat, mode_def, mat_settings);
    }
    VIEWER.material_freeShadingModes(index);
    VIEWER.mat_shading_modes[index] = mat_shading_modes; {
        var list_shaders = new Array();
        for (var i = 0; i < shaderkeys.length; i++) {
            var mode_def = VIEWER.viewer_shading_modes[shaderkeys[i]];
            if (mode_def.type == 'shader') {
                list_shaders.push($.extend({}, mode_def));
            }
        }
        VIEWER.setUniforms(index, list_shaders, mat_settings, mat_textures, mat_shading_modes);
        for (var i = 0; i < list_shaders.length; i++) {
            var name = list_shaders[i].shader_name;
            var parameters = {
                fragmentShader: list_shaders[i].fragShader,
                vertexShader: list_shaders[i].vertShader,
                uniforms: list_shaders[i].uniforms,
                lights: VIEWER.shader_params[name].threejs_lights
            };
            mat_shading_modes[list_shaders[i].key] = new THREE.ShaderMaterial(parameters);
            mat_shading_modes[list_shaders[i].wireonname][0] = mat_shading_modes[list_shaders[i].key];
        }
    }
};

VIEWER.LoadModel = function (jspath) {
    var mesh = null;
    var loader = new THREE.JSONLoader();
    loader.load('', jspath, function (geometry, materials) {

        VIEWER.scene_freeGeometry();
        console.log('geometry loaded');
        VIEWER.orig_geometry = geometry;
        if (VIEWER.orig_geometry.faces && VIEWER.orig_geometry.faces.length > 0) {
            if (!VIEWER.orig_geometry.faces[0].vertexNormals || VIEWER.orig_geometry.faces[0].vertexNormals.length == 0 || VIEWER.orig_geometry.faces[0].vertexNormals[0].lengthSq() == 0.0) {
                console.log('computing vertex normals');
                var start = new Date().getTime();
                VIEWER.computeNormals(VIEWER.orig_geometry);
                console.log('computing vertex normals duration: ' + (new Date().getTime() - start));
            }
        }
 /*       VIEWER.recreateMaterials();
        P3DV.prepareMesh(P3DV.orig_geometry, true);
        P3DV.orig_geometry.dynamic = true;
        var subd = (P3DV.viewer_settings.subdivision) ? 1 : 0;
        P3DV.updateGeometry(subd);
        if (P3DV.mesh) P3DV.world.remove(P3DV.mesh);
        P3DV.mesh = new THREE.Mesh(P3DV.render_geometry, P3DV.mat_shading_modes[0].smooth);
        P3DV.mesh.flipSided = false;
        P3DV.mesh.overdraw = true;
        if (P3DV.model_settings.orientation)
            P3DV.setThreeRotation(P3DV.mesh.rotation, P3DV.model_settings.orientation);
        P3DV.world.position.x = 0;
        P3DV.world.position.z = 0;
        P3DV.world.position.y = 0;
        if (!geometry.boundingSphere) {
            geometry.computeBoundingSphere();
        }
        P3DV.world.scale.x = P3DV.world.scale.y = P3DV.world.scale.z = (P3DV.initial_size / geometry.boundingSphere.radius);
        if (P3DV.model_settings.default_camera) {
            var dc = P3DV.model_settings.default_camera
            if ("world_rot" in dc) {
                P3DV.setThreeRotation(P3DV.world.rotation, dc.world_rot);
            }
        }
        P3DV.world.add(P3DV.mesh);
        P3DV.force_render_frames = 4;
        if (P3DV.viewer_settings.autorotate) {
            P3DV.scene_time = new Date().getTime();
        }
        if (P3DV.viewer_settings.shading) {
            P3DV.updateShader('smooth');
        } else {
            P3DV.viewer_settings.shading = "smooth";
        }
        P3DV.refreshShader();
        if (P3DV.callbacks.updateViewerPreviews) {
            P3DV.callbacks.updateViewerPreviews();
        }
*/

        mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
        mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
        mesh.translation = THREE.GeometryUtils.center(geometry);
        VIEWER.scene.add(mesh);

        VIEWER.fitAll(VIEWER.scene);
    });


};


